﻿#define _CAMERABOX_TRACKBAR_ENABLE

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

using Declarations;
using Declarations.Events;
using Declarations.Media;
using Declarations.Players;
using Implementation;

namespace SDK10kWrapper
{
    public enum CameraMode { Online, Offline };
    public enum CameraSyncMode { None, Master, Slave };
    public partial class CameraBox : UserControl, INotifyPropertyChanged
    {
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool CheckPropertyChanged<T>
        (string propertyName, ref T oldValue, ref T newValue)
        {
            if (oldValue == null && newValue == null)
            {
                return false;
            }

            if ((oldValue == null && newValue != null) || !oldValue.Equals((T)newValue))
            {
                oldValue = newValue;
                OnPropertyChanged(propertyName);
                return true;
            }

            return false;
        }

        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            /*System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }*/
        }
        #endregion

        #region PUBLIC PROPERTIES
        #region Online Property
        string _Online_IP = "192.168.1.1";
        [DefaultValue("192.168.1.1"), Category(".Camera"), Description("Camera Server IP Address")]
        public string Online_IP { get { return _Online_IP; } set { _Online_IP = value; } }

        string _Online_UserName = "Admin";
        [DefaultValue("Admin"), Category(".Camera"), Description("Camera Server Username")]
        public string Online_UserName { get { return _Online_UserName; } set { _Online_UserName = value; } }

        string _Online_Password = "123456";
        [DefaultValue("123456"), Category(".Camera"), Description("Camera Server Password")]
        public string Online_Password { get { return _Online_Password; } set { _Online_Password = value; } }

        [Category(".Camera"), Description("Check if Camera is Recording")]
        public bool Online_IsRecording
        {
            get
            {
                if ((_CameraMode == SDK10kWrapper.CameraMode.Online) && (m_cam != null)) return m_cam.IsRecording;
                else if ((_CameraMode == SDK10kWrapper.CameraMode.Offline) && (m_player != null)) return false;
                else return false;
            }
        }

        bool _Online_IsConnected = false;
        [Category(".Camera"), Description("Check if Camera is Connected to Server")]
        public bool Online_IsConnected
        {
            get { return _Online_IsConnected; }
            set
            {
                if (value != _Online_IsConnected) // To avoid value rebind again & again (dead loop) then have to check for value.
                {
                    _Online_IsConnected = value;
                    OnPropertyChanged("Online_IsConnected");
                }
            }
        }

        string _Online_VideoSavePath = "Please CHANGE ME !"; // Don't forget to implement Init Value to m_cam.SavePath in _Load() Event
        [DefaultValue("N/A"), Category(".Camera"), Description("Specify where to Save your Recording File")]
        public string Online_VideoSavePath
        {
            get { if (m_cam != null) return m_cam.SavePath; else return _Online_VideoSavePath; }
            set { if (m_cam != null) m_cam.SavePath = value; else _Online_VideoSavePath = value; }
        }
        #endregion
        #region Offline Property
        string _Offline_VideoPlayPath = "Please CHANGE ME !";
        [DefaultValue("N/A"), Category(".Camera"), Description("Specify where to Load your Video Playback File")]
        public string Offline_VideoPlayPath { get { return _Offline_VideoPlayPath; } set { _Offline_VideoPlayPath = value; } }


        #endregion
        #region Both Property
        CameraSyncMode _CameraSyncMode = CameraSyncMode.None;
        /// <summary>
        /// Do not Set this Property in Runtime Code. Set this in Designer only for Synchronization or not
        /// </summary>
        [DefaultValue(CameraMode.Offline), Category(".Camera"), Description("Specify Camera Mode Operation")] //Browsable(false)
        public CameraSyncMode CameraSyncMode
        {
            get { return _CameraSyncMode; }
            set
            {
                this._CameraSyncMode = value;

                // If Master then other set to Slave
                if (_CameraSyncMode == SDK10kWrapper.CameraSyncMode.Master)
                    foreach (CameraBox cambox in cList) // Always remember foreach already include this._
                    {
                        if (cambox.ParentForm == this.ParentForm && cambox != this) cambox._CameraSyncMode = CameraSyncMode.Slave;
                    }
            }
        }

        private void SwitchSyncMode()
        {

        }

        CameraMode _CameraMode = CameraMode.Offline;
        [DefaultValue(CameraMode.Offline), Category(".Camera"), Description("Specify Camera Mode Operation")]
        public CameraMode CameraMode
        {
            get { return _CameraMode; }
            set
            {
                if (value == SDK10kWrapper.CameraMode.Offline)
                { label.Text = "OFFLINE"; status.Visible = false; }
                else { label.Text = "ONLINE"; status.Visible = true; }

                if (m_cam != null)
                {
                    if (value == SDK10kWrapper.CameraMode.Online)
                    {
                        if (_CameraMode == SDK10kWrapper.CameraMode.Offline) this.Stop(); // If from Offline to Online then Stop Offline_Player first
                        Online_IsConnected = m_cam.Connect();
                    }
                    else if (_CameraMode == SDK10kWrapper.CameraMode.Offline) { m_cam.Disconnect(); Online_IsConnected = false; }
                }

                _CameraMode = value;
            }
        }

        [Category(".Camera"), Description("Check if Camera is Playing")]
        public bool IsPlaying
        {
            get
            {
                if ((_CameraMode == SDK10kWrapper.CameraMode.Online) && (m_cam != null)) return m_cam.IsPlaying;
                else if ((_CameraMode == SDK10kWrapper.CameraMode.Offline) && (m_player != null)) return m_player.IsPlaying;
                else return false;
            }
        }

        string _CapturePath = "Please CHANGE ME !";
        [DefaultValue("N/A"), Category(".Camera"), Description("Specify where to Save your Capture File")]
        public string CapturePath { get { return _CapturePath; } set { _CapturePath = value; } }

        #endregion
        #endregion

        #region PUBLIC METHODS
        #region Online Method
        /// <summary>
        /// CONNECT TO THIS CAMERA (server) & READY TO GET DATA.
        /// </summary>
        /// <returns>TRUE if connect SUCCESS otherwise FALSE</returns>
        public bool Connect()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                MessageBox.Show("Camera now in Offline Mode ...\r\nChange CameraMode to Online First !", m_cam.Name);
                return false;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                Online_IsConnected = m_cam.Connect();
                return Online_IsConnected;
            }
            else return false;
        }

        /// <summary>
        /// DISCONNECT FROM THIS CAMERA (server) & STOP GETTING DATA.
        /// </summary>
        /// <returns>TRUE if disconnect SUCCESS otherwise FALSE</returns>
        public bool Disconnect()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                MessageBox.Show("Camera now in Offline Mode !", m_cam.Name);
                return false;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                Online_IsConnected = false;
                return m_cam.Disconnect();
            }
            else return false;
        }

        /// <summary>
        /// <para>START RECORDING THIS CAMERA IN REALTIME TO A FILE.</para>
        /// <para>To change File Path set the Online_SavePath</para>
        /// </summary>
        /// <returns>TRUE if record SUCCESS otherwise FALSE</returns>
        public bool StartRecord()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                MessageBox.Show("Camera now in Offline Mode !", m_cam.Name);
                return false;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                return m_cam.StartRecord();
            }
            else return false;
        }

        /// <summary>
        /// STOP RECORDING THIS CAMERA &amp; RETURN THE FILEINFO.
        /// </summary>
        /// <returns>VALID FileInfo if record SUCCESS otherwise NULL</returns>
        public FileInfo StopRecord()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                MessageBox.Show("Camera now in Offline Mode !", m_cam.Name);
                return null;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                return m_cam.StopRecord();
            }
            else return null;
        }
        #endregion
        #region Offline Method

        #endregion
        #region Both Method
        bool justPlay = false;
        /// <summary>
        /// <para>PLAY THIS CAMERA (depend on CameraMode you set).</para>
        /// <para>Offline will playback movie setted in Offline_FilePath, Online will play streaming realtime from server</para>
        /// </summary>
        /// <returns>TRUE if play SUCCESS otherwise FALSE</returns>
        public bool Play()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                // Check if File to Play exist
                if (!File.Exists(_Offline_VideoPlayPath)) { MessageBox.Show(string.Format("{0}\r\nThis \"_Offline_FilePath\" does not exist, Fail to Play !", _Offline_VideoPlayPath), m_cam.Name); label.Visible = true; return false; }

                if (m_player.IsPlaying) { /*if (m_media != null) m_media.Dispose(); */ m_player.Stop(); }// Need to Dispose m_media before Stop

                m_media = m_factory.CreateMedia<IMedia>(_Offline_VideoPlayPath);
                m_media.Events.DurationChanged += new EventHandler<MediaDurationChange>(Events_DurationChanged);
                m_media.Events.StateChanged += new EventHandler<MediaStateChange>(Events_StateChanged);
                m_media.Events.ParsedChanged += new EventHandler<MediaParseChange>(Events_ParsedChanged);

                m_player.Open(m_media);
                m_media.Parse(true);
                m_player.Play(); // Consider move after lock(locker)

                justPlay = true;

#if IMPLEMENTED_BINDING_ANYTRACKBAR
                lock (locker) // Chi tung thang duoc phep thay doi cai nay ma thoi
                {   // Resource use in this lock (duration : only update here | cList : also use lock already)
                    this.duration = m_player.Length; // Do we really need duration khong nhi ? or use direct m_player.Length

                    long temp = 0; foreach (CameraBox cam in cList)
                    {
                        if (cam == null) continue; if (cam.Disposing) continue; if (cam.IsDisposed) continue;
                        if (cam.duration > temp) try { temp = cam.duration; }
                            catch { }
                    }

                    MaxDuration = temp; // Multi cambox can access MaxDuration at the sametime.
                }
#endif
                // After Open new m_media, the Position will be reset to -1
                //float pos = (float)_Position / _TrackBar.Maximum;
                //m_player.Position = pos > 1 ? 1 : pos;

                label.Visible = false; return true;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                if (m_cam.Play() == true) { label.Visible = false; return true; }
                else { label.Visible = true; return false; }
            }
            else return false;
        }

        /// <summary>
        /// <para>PAUSE THIS CAMERA (depend on CameraMode you set).</para>
        /// <para>Offline will pause movie setted in Offline_FilePath, Online will pause streaming realtime from server</para>
        /// </summary>
        /// <returns>TRUE if pause SUCCESS otherwise FALSE</returns>
        public bool Pause()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                m_player.Pause();
                return true;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                return m_cam.Pause();
            }
            else return false;
        }

        /// <summary>
        /// <para>STOP THIS CAMERA (depend on CameraMode you set).</para>
        /// <para>Offline will stop movie setted in Offline_FilePath, Online will stop streaming realtime from server</para>
        /// </summary>
        /// <returns>TRUE if stop SUCCESS otherwise FALSE</returns>
        public bool Stop()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                //if (m_media != null) m_media.Dispose();
                m_player.Stop();
                label.Visible = true;
                return true;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                label.Visible = true;
                return m_cam.Stop();
            }
            else return false;
        }

        /// <summary>
        /// TURN ON SOUND DURING PLAYING CAMERA
        /// </summary>
        /// <returns>TRUE if on sound SUCCESS otherwise FALSE</returns>
        public bool SoundON()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                m_player.Mute = false;
                return true;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                return m_cam.SoundON();
            }
            else return false;
        }

        /// <summary>
        /// TURN OFF SOUND DURING PLAYING CAMERA
        /// </summary>
        /// <returns>TRUE if off sound SUCCESS otherwise FALSE</returns>
        public bool SoundOFF()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                m_player.Mute = true;
                return true;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                return m_cam.SoundOFF();
            }
            else return false;
        }

        /// <summary>
        /// Capture Entire Camera Frame into .PNG file
        /// </summary>
        /// <param name="savePath">File Path to save PNG capture into</param>
        /// <returns>null if Capture Fail otherwise a FileInfo</returns>
        public FileInfo Capture(string savePath)
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                m_player.TakeSnapShot(0, savePath);
                if (File.Exists(savePath)) return new FileInfo(savePath);
                else return null;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                MessageBox.Show("Implement later !", "Online Capture : " + m_cam.Name);
                return null;
            }
            else return null;
        }

        /// <summary>
        /// Capture Entire Camera Frame into .PNG File set in CapturePath
        /// </summary>
        /// <returns>null if Capture Fail otherwise a FileInfo</returns>
        public new FileInfo Capture()
        {
            if (this.CameraMode == CameraMode.Offline)
            {
                m_player.TakeSnapShot(0, _CapturePath);
                if (File.Exists(_CapturePath)) return new FileInfo(_CapturePath);
                else return null;
            }
            else if (this.CameraMode == CameraMode.Online)
            {
                MessageBox.Show("Implement later !", "Online Capture : " + m_cam.Name);
                return null;
            }
            else return null;
        }
        #endregion
        #endregion

        #region INITIAL CODE
        // VLC private variable
        IMediaPlayerFactory m_factory;
        IDiskPlayer m_player;
        IMedia m_media;
        // SDK10000 private variable
        Camera m_cam;

        /// <summary>
        /// <para>This UserControl wrap both SDK10000 &amp; VLC dll, support Offline &amp; Online Mode.</para>
        /// <para>To change operation mode for this camera, set the &quot;CameraMode&quot; Property.</para>
        /// </summary>
        public CameraBox()
        {
            InitializeComponent();
            lock (locker) cList.Add(this);
            //System.Threading.Interlocked.Increment(ref m_count); // Consider to return Camera.Count but need to implement Interlock for Camera Class also.
        }

        private void CameraBox_Load(object sender, EventArgs e)
        {
            if (Util.DesignMode) return; // http://stackoverflow.com/questions/3322151/problem-loading-usercontrol-with-dll-reference-in-design-view
            
            // Init VLC
            List<string> args = new List<string>(new string[]
                {
                    "-I", 
                    "dumy",  
                    "--ignore-config", 
                    "--no-osd",
                    "--disable-screensaver",
                    "--ffmpeg-hw",
                    "--plugin-path=./plugins" 
                });
            
            if (this._CameraSyncMode == SDK10kWrapper.CameraSyncMode.Master)
            { args.Add("--control"); args.Add("netsync"); args.Add("--netsync-master"); }
            else if (this._CameraSyncMode == SDK10kWrapper.CameraSyncMode.Slave)
            { args.Add("--control"); args.Add("netsync"); args.Add("--netsync-master-ip"); args.Add("\"127.0.0.1\""); }

            m_factory = new MediaPlayerFactory(false, args.ToArray());
            m_player = m_factory.CreatePlayer<IDiskPlayer>();

            m_player.WindowHandle = panel.Handle; // Set Panel as the Form
            m_player.Volume = 100; // Set default maximum volume, so user set windows volume only
            m_player.Position = _Offline_VideoPlayPosition; // Set this for special property

            m_player.Events.PlayerPositionChanged += new EventHandler<MediaPlayerPositionChanged>(Events_PlayerPositionChanged);
            m_player.Events.TimeChanged += new EventHandler<MediaPlayerTimeChanged>(Events_TimeChanged);
            m_player.Events.MediaEnded += new EventHandler(Events_MediaEnded);
            m_player.Events.PlayerStopped += new EventHandler(Events_PlayerStopped);

            UISync.Init(this);

            // Init SDK10k
            m_cam = new Camera(panel, _Online_IP, _Online_UserName, _Online_Password, _Online_VideoSavePath);
            m_cam.SavePath = _Online_VideoSavePath; // Set this for special property

            Binding b = new Binding("Text", this, "Online_IsConnected");
            b.Format += (s, arg) =>
                {
                    arg.Value = (bool)arg.Value ? "CONNECTED" : "NOT CONNECT";
                };
            status.DataBindings.Add(b);
        }
        #endregion

        //====================================================================//

        #region IDisposable
        /// <summary>
        /// Call this method in Dispose of this UserControl to Release Unmanaged Resources
        /// </summary>
        public void Release() //http://stackoverflow.com/questions/5069391/whats-the-purpose-of-the-components-icontainer-generated-by-the-winforms-design
        {
            if (Util.DesignMode) return;
            MessageBox.Show("FUCK");
            try
            {
                // Dispose your own object
                if (m_cam != null) m_cam.Dispose();
                if (m_player != null) { m_player.Stop(); m_player.Dispose(); } // Remember to stop player first
                if (m_media != null) m_media.Dispose();
                if (m_factory != null) m_factory.Dispose();
#if _CAMERABOX_TRACKBAR_ENABLE
                // Unbinding with Trackbar
                for (int i = 0; i < tbDict.Count; i++)
                {
                    this.TrackBarUnbinding(tbDict.Keys.ElementAt(i--));
                }
#endif
                // Remove Reference Instance
                lock (locker) cList.Remove(this);
                //System.Threading.Interlocked.Decrement(ref m_count);
            }
            catch (Exception e) { MessageBox.Show(e.Message, "CameraBox Release() Fail"); }
        }
        #endregion
    }

    static class Util
    {
        /// <summary>
        /// Contains true, if we are in design mode of Visual Studio
        /// </summary>
        static bool _designMode;

        /// <summary>
        /// Initializes an instance of Util class
        /// </summary>
        static Util()
        {
            // design mode is true if host process is: Visual Studio,
            // Visual Studio Express Versions (C#, VB, C++) or SharpDevelop
            var designerHosts = new List<string>() {
            "devenv", "vcsexpress", "vbexpress", "vcexpress", "sharpdevelop" };

            using (var process = System.Diagnostics.Process.GetCurrentProcess())
            {
                var processName = process.ProcessName.ToLower();
                _designMode = designerHosts.Contains(processName);
            }
        }

        /// <summary>
        /// Gets true, if we are in design mode of Visual Studio etc..
        /// </summary>
        public static bool DesignMode
        {
            get
            {
                return _designMode;
            }
        }
    }
}
